#include "stdafx.h"
#include "LowLevel.h"

using namespace sndcore;

short sndcore::Distortion(short sample, short threshold, BYTE bitsPerSample)
//-------------------------------------------------------------------------------
/*!
\brief Distorts a sample using a hard cut-off.
\param sample
	Sample value to distort
\param threshold
	Threshold value to begin distorting at
\param bitsPerSample
	8 or 16 depending on bitspersample of original channel
\return
	Distorted sample value
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	if (bitsPerSample == 8) {
		//Convert to neg/pos range
		sample -= BYTE_RANGE;
		threshold -= BYTE_RANGE;
	}

	//Work here!
	int iSamp = sample;
	//if (iSamp > threshold || iSamp < -threshold)
		//iSamp = abs(abs((iSamp - threshold) % (threshold*4)) - threshold*2) - threshold;
	if (iSamp > threshold)	
		iSamp = (int)(threshold + (iSamp-threshold)/2.f);
	else if (iSamp < -threshold)
		iSamp = (int)(-threshold - (threshold-iSamp)/2.f);

	//Fix the volume loss
	//threshold = threshold*4;
	float gainfix = 1.f;//bitsPerSample == 16 ? WORD_RANGE/(float)threshold : BYTE_RANGE/(float)threshold;
	gainfix = gainfix < 1.f ? 1.f : gainfix;
	//Return
	sample = (short)(iSamp*gainfix);

	return bitsPerSample == 8 ? sample + BYTE_RANGE : sample;
}

bool sndcore::LowLevelResample(const Sound& srcSound,const WAVEFORMATEX* pDstFormat, float pitchSteps, Sound** dstSound)
//-------------------------------------------------------------------------------
/*!
\brief  Resamples a sound to any given format.  Here, pitch changes can also be
		specified.
\param	srcSound
		The source sound to resample.
\param  pDstFormat
		Pointer to destination format to resample to.
\param	pitchSteps
		Number of octaves up (+) or down (-) to shift source pitch by.
\param  dstSound
		Catches the final, allocated, destination sound.
\return	Returns true if resampling worked, or false otherwise.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	const WAVEFORMATEX& srcFormat = srcSound.GetFormat();
	WAVEFORMATEX        dstFormat = *pDstFormat;

	//Safety checks ////////////////////////////////////////
	assert(srcFormat.wBitsPerSample == 8 || srcFormat.wBitsPerSample == 16 ||
		   dstFormat.wBitsPerSample == 8 || dstFormat.wBitsPerSample == 16);
	assert(srcFormat.nChannels == 1 || srcFormat.nChannels == 2 ||
		   dstFormat.nChannels == 1 || dstFormat.nChannels == 2);

	//Make the pitch value make sense
	pitchSteps = pow(2,pitchSteps);

	//Apply Pitch Shifting /////////////////////////////////
	dstFormat.nSamplesPerSec = (DWORD)(pitchSteps*dstFormat.nSamplesPerSec);

	//Data to create new sound /////////////////////////////
	FLOAT sampleRateRatio = (FLOAT)dstFormat.nSamplesPerSec/srcSound.GetFormat().nSamplesPerSec;
	FLOAT blockSizeRatio  = (FLOAT)dstFormat.nBlockAlign/srcSound.GetFormat().nBlockAlign;
	FLOAT combinedRatio   = sampleRateRatio*blockSizeRatio;

	//Alloc data for destination sound /////////////////////
	assert(*dstSound == NULL && "Destination sound not NULL");
	SoundBufferOptions options = {0};
	options.format_ = dstFormat;
	options.size_    = (DWORD)(srcSound.buffer_->GetSize()*combinedRatio);
	options.songlen_ = srcSound.buffer_->GetOptions().songlen_;
	*dstSound = new Sound(new SoundBufferUser(options), srcSound.GetStream(), srcSound.GetId() );

	//Loop through samples on the destination sound ////////
	if (srcFormat.nSamplesPerSec <= dstFormat.nSamplesPerSec)
		return LowLevelUpsample(srcSound,**dstSound,srcFormat,dstFormat);
	return LowLevelDownsample(srcSound,**dstSound,srcFormat,dstFormat);
}

bool sndcore::LowLevelResampleLerp(const Sound& srcSound, Sound& dstSound, DWORD srcIndexA, DWORD srcIndexB, DWORD dstIndexA, DWORD dstIndexB, SNDLRPOPTIONS options)
//-------------------------------------------------------------------------------
/*!
\brief  Linear Interpolates between two sample values and produces an arbitrary
		 number of intermediate interpolated values.
\param  srcSound
		 The source sound that is being resampled.
\param	dstSound
		 Destination sound where resampled data is being placed.
\param  srcIndexA
		 First index into the source sound (to begin interpolation from)
\param  srcIndexB
		 Second index into the source sound (to end interpolation)
\param  dstIndexA
		 First index into the destination sound (to interpolate to)
\param  dstIndexB
		 Second index into the destination sound (to end interpolation to)
\param	options
		 Options that tell the linear interpolation how to mix sounds.
\return	True on success, false otherwise.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	//Don't Lerp if there is nothing t lerp over ///////////
	if (dstIndexB - dstIndexA == 0)
		return false;

	const WAVEFORMATEX& dstFormat   = dstSound.GetFormat();
	const WAVEFORMATEX& srcFormat   = srcSound.GetFormat();
	const WORD			srcChannels = srcSound.GetFormat().nChannels;
	const WORD			dstChannels = dstSound.GetFormat().nChannels;

	//Need to get at the src and dest data /////////////////
	DWORD srcSize=0,dstSize=0;
	LPBYTE  srcData=NULL;
	LPBYTE  dstData=NULL;
	srcData = (LPBYTE)srcSound.buffer_->DirectRead(&srcSize);
	dstData = (LPBYTE)dstSound.buffer_->DirectRead(&dstSize);

	//Set Values ///////////////////////////////////////////
	SHORT valA=0,valB=0,valC=0,valD=0;
	//If 8 to 16
	if (srcFormat.wBitsPerSample == 8 && dstFormat.wBitsPerSample == 16) {
		valA = SAMPLE8TO16(srcData[srcIndexA*srcChannels]);
		valB = SAMPLE8TO16(srcData[srcIndexB*srcChannels]);
		if (srcChannels == 2) {
			valC = SAMPLE8TO16(srcData[srcIndexA*srcChannels+1]);
			valD = SAMPLE8TO16(srcData[srcIndexB*srcChannels+1]);
		}
	}
	//If 8 to 8
	else if (srcFormat.wBitsPerSample == 8 && dstFormat.wBitsPerSample == 8) {
		valA = srcData[srcIndexA*srcChannels];
		valB = srcData[srcIndexB*srcChannels];
		if (srcChannels == 2) {
			valC = srcData[srcIndexA*srcChannels+1];
			valD = srcData[srcIndexB*srcChannels+1];
		}
	}
	//If 16 to 16
	else if (srcFormat.wBitsPerSample == 16 && dstFormat.wBitsPerSample == 16) {
		valA = ((LPSHORT)srcData)[srcIndexA*srcChannels];
		valB = ((LPSHORT)srcData)[srcIndexB*srcChannels];
		if (srcChannels == 2) {
			valC = ((LPSHORT)srcData)[srcIndexA*srcChannels+1];
			valD = ((LPSHORT)srcData)[srcIndexB*srcChannels+1];
		}
	}
	//If 16 to 8
	else {
		valA = SAMPLE16TO8(((LPSHORT)srcData)[srcIndexA*srcChannels]);
		valB = SAMPLE16TO8(((LPSHORT)srcData)[srcIndexB*srcChannels]);
		if (srcChannels == 2) {
			valC = SAMPLE16TO8(((LPSHORT)srcData)[srcIndexA*srcChannels+1]);
			valD = SAMPLE16TO8(((LPSHORT)srcData)[srcIndexB*srcChannels+1]);
		}
	}

	//Do we want to copy the first channel into both destination channels?
	if (options == SLO_COPY) {
		valC = valA; valD = valB;
	}
	else if (options == SLO_COMBINE) {
		valA = MixSamples(valA,valC,1.f,1.f,dstFormat.wBitsPerSample);
		valB = MixSamples(valB,valD,1.f,1.f,dstFormat.wBitsPerSample);
	}

	//Lerp between the two points //////////////////////////
	for (DWORD dstSample = dstIndexA; dstSample <= dstIndexB; dstSample++) {
		//First channel ////////////////////////////////////////
		FLOAT percent = (FLOAT)(dstSample-dstIndexA)/(dstIndexB-dstIndexA);
		SHORT valC1   = (SHORT)(percent*(valB-valA) + valA);
		if (dstFormat.wBitsPerSample == 16)
			((LPSHORT)dstData)[dstSample*dstChannels] = valC1;
		else
			dstData[dstSample*dstChannels] = (BYTE)valC1;

		//Second channel ///////////////////////////////////////
		if (dstChannels == 2) {
			SHORT valC2   = (SHORT)(percent*(valD-valC) + valC);
			if (dstFormat.wBitsPerSample == 16) {
				((LPSHORT)dstData)[dstSample*dstChannels+1] = valC2;
			}
			else
				dstData[dstSample*dstChannels+1] = (BYTE)valC2;
		}
	}
	return true;
}

bool sndcore::LowLevelDownsample(const Sound& srcSound, Sound& dstSound, const WAVEFORMATEX& srcFormat, const WAVEFORMATEX& dstFormat)
//-------------------------------------------------------------------------------
/*!
\brief  Downsamples a source sound to a destination format.
\param  srcSound
		Source sound to be resampled.
\param	dstSound
		Destination sound to write resampled data to.
\param	srcFormat
		Source sound format.
\param	dstFormat
		Destination sound format.
\return True on success, false otherwise.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	//Get variables
	DWORD srcSize      = srcSound.GetSize();
	DWORD dstSize      = dstSound.GetSize();
	DWORD dstSamples   = dstSize/dstFormat.nBlockAlign;
	DWORD srcSamples   = srcSize/srcFormat.nBlockAlign;

	//Get data
	LPBYTE dstData  = (LPBYTE)dstSound.buffer_->DirectRead();
	LPBYTE srcData  = (LPBYTE)srcSound.buffer_->DirectRead();

	//Loop through destination and sample
	for (DWORD dstSample = 0; dstSample < dstSamples; ++dstSample) {
		const DWORD dstIndex = dstSample;
		const DWORD srcIndex = (DWORD)((dstIndex/(FLOAT)(dstSize-1))*srcSamples);
		
		//Choose sample bit conversion type
		if (srcFormat.wBitsPerSample == 8 && dstFormat.wBitsPerSample == 16) {
			((LPSHORT)dstData)[dstIndex] = SAMPLE8TO16(srcData[srcIndex]);
		}
		else if (srcFormat.wBitsPerSample == 8 && dstFormat.wBitsPerSample == 8) {
			dstData[dstIndex] = srcData[srcIndex];
		}
		else if (srcFormat.wBitsPerSample == 16 && dstFormat.wBitsPerSample == 16) {
			((LPSHORT)dstData)[dstIndex] = ((LPSHORT)srcData)[srcIndex];
		}
		else {
			dstData[dstIndex] = SAMPLE16TO8(((LPSHORT)srcData)[srcIndex]);
		}
	}
	return true;
}

bool sndcore::LowLevelUpsample(const Sound& srcSound, Sound& dstSound, const WAVEFORMATEX& srcFormat, const WAVEFORMATEX& dstFormat)
//-------------------------------------------------------------------------------
/*!
\brief  Upsamples a source sound to a destination format.
\param  srcSound
Source sound to be resampled.
\param	dstSound
Destination sound to write resampled data to.
\param	srcFormat
Source sound format.
\param	dstFormat
Destination sound format.
\return True on success, false otherwise.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	//Get variables
	const DWORD dstSize      = dstSound.GetSize();
	const DWORD srcSize      = srcSound.GetSize();
	const DWORD dstSamples   = dstSize/dstFormat.nBlockAlign;
	const DWORD srcSamples   = srcSize/srcFormat.nBlockAlign;

	//Set lerp options
	SNDLRPOPTIONS sloOptions;
	if (srcFormat.nChannels == 1 && dstFormat.nChannels == 1)
		sloOptions = SLO_MONO;
	else if (srcFormat.nChannels == 2 && dstFormat.nChannels == 2)
		sloOptions = SLO_STEREO;
	else if (srcFormat.nChannels == 1 && dstFormat.nChannels == 2)
		sloOptions = SLO_COPY;
	else 
		sloOptions = SLO_COMBINE;

	//Loop through source and LERP
	for (DWORD srcSample = 0; srcSample < srcSamples; ++srcSample) {
		//Find indices we need to LERP /////////////////////
		const DWORD srcIndexA = srcSample;
		const DWORD srcIndexB = srcSample < srcSamples - 1 ? srcSample+1 : srcSamples - 1;
		const DWORD dstIndexA = (DWORD)((srcIndexA/(FLOAT)(srcSamples-1))*(dstSamples-1));
		const DWORD dstIndexB = (DWORD)((srcIndexB/(FLOAT)(srcSamples-1))*(dstSamples-1));
		LowLevelResampleLerp(srcSound, dstSound, srcIndexA, srcIndexB, dstIndexA, dstIndexB, sloOptions);
	}
	return true;
}

bool sndcore::Pan(short& lChannel, short& rChannel, float panVal, BYTE bitsPerSample)
//-------------------------------------------------------------------------------
/*!
\brief  Performs panning on sound channels.
\param  lChannel
		Source sample for the left channel.  Overwritten with panned value.
\param  rChannel
		Source sample for the right channel.  Overwritten with panned value.
\param	panVal
		Panning value between 0 and 1.  0 = left, 0.5 = center, 1 = right.
\param bitsPerSample
		8 or 16 depending on bitspersample of original channel
\return True on success, false otherwise.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	float s = .5f*PI*panVal;
	float lg = sin(s), rg = cos(s);
	if (bitsPerSample == 16) {
		lChannel = (short)(lChannel*lg);
		rChannel = (short)(rChannel*rg);
		return true;
	}
	lChannel = (short)((lChannel-BYTE_RANGE)*lg+BYTE_RANGE);
	rChannel = (short)((rChannel-BYTE_RANGE)*rg+BYTE_RANGE);
	return true;
}


short sndcore::MixSamples( short leftval, short rightval, float left_gain, float masterVol, int bitsPerSample  )
//-------------------------------------------------------------------------------
/*!
\brief  Mixes two samples together and returns the mixed sample.
\param  leftval
		First sample to mix.
\param  rightval
		Second sample to mix.
\param	left_gain
		Gain to apply to the left sample.
\param  masterVol
		Master application volume.
\param bitsPerSample
		8 or 16 depending on bitspersample of original channel
\return The mixed sample.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	short ret = 0;


	if( bitsPerSample == 8 )
	{
		CHAR intermediate = (CHAR)(((SHORT)leftval-BYTE_RANGE) * left_gain * masterVol );
		leftval = intermediate+BYTE_RANGE;


		if( rightval <= BYTE_RANGE && leftval <= BYTE_RANGE )
			ret = ( rightval * leftval ) >> 7;
		else
			ret = 2 * ( rightval + leftval ) - 
			( ( rightval * leftval ) >> 7 ) - 257;
	}
	else if( bitsPerSample == 16 )
	{
		DWORD a = (DWORD) ( rightval + WORD_RANGE );
		DWORD b = (DWORD) (  leftval * left_gain * masterVol + WORD_RANGE );
		DWORD apb = a+b;
		DWORD atb = a*b;
		INT atbd = (atb>>15) + 1;

		if( a <= WORD_RANGE && b <= WORD_RANGE)
			ret = (SHORT)(atbd-WORD_RANGE);
		else
			ret = (SHORT)((apb<<1) - atbd - 65537 -WORD_RANGE);
	}


	return ret;
}